﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using System.Threading.Tasks;
using System.Data;
using System.Data.SqlClient;
using BEELab.Logic.ProgramManagement.SessionManagement;
using System.Text;
using System.Threading;

namespace BEELab.Logic.Execution
{
    public class MessageHub : Hub
    {
        private List<string> SystemVariables = (new string[] { "SubjectID", "SubjectList", "SubjectCount", "NextStage", "CurrentStage" }).ToList();

        /// <summary>
        /// Function that releases ALL locks that held by a specific user
        /// </summary>
        /// <param name="SubjectID">User's subject ID</param>
        public void Desert(string SubjectID)
        {
            //release all locks
            foreach (BEELab.Logic.ProgramManagement.VariableManagement.VariableManagement.Variable variable in Global.SessionVariables[SessionManagement.GetSessionIDBySubjectID(SubjectID)].Values)
            {
                if (variable.isLocked && variable.lockOwner == SubjectID)
                {
                    variable.Release(SubjectID);
                }
            }
        }

        public void GetValue(string SubjectID, string VariableName, string callback)
        {
            int SessionID = BEELab.Logic.ProgramManagement.SessionManagement.SessionManagement.GetSessionIDBySubjectID(SubjectID);

            if (Global.SessionVariables[SessionID].ContainsKey(VariableName) == false)
            {
                return;
            }

            Clients.Client(Global.SubjectConnetionDictionary[SubjectID]).setVariableAndInvoke(SubjectID, VariableName, Global.SessionVariables[SessionID][VariableName].Value, callback);
        }

        public string SetValue(string SubjectID, string VariableName, string Value, string callback)
        {
            if (SystemVariables.Contains(VariableName))
                return null;

            int SessionID = BEELab.Logic.ProgramManagement.SessionManagement.SessionManagement.GetSessionIDBySubjectID(SubjectID);

            if (Global.SessionVariables[SessionID].ContainsKey(VariableName) == false)
            {
                return null;
            }

            if (Global.SessionVariables[SessionID][VariableName].isLocked == false || Global.SessionVariables[SessionID][VariableName].lockOwner == SubjectID)
            {
                Global.SessionVariables[SessionID][VariableName].Value = Value;
                Clients.Client(Global.SubjectConnetionDictionary[SubjectID]).invoke(callback);
                return Global.SessionVariables[SessionID][VariableName].Value;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Locks a variable
        /// </summary>
        /// <param name="SubjectID">Subject ID</param>
        /// <param name="VariableName">Variable Name</param>
        /// <param name="callback">Callback</param>
        /// <returns>True if succeeded, False otherwise</returns>
        public Boolean Lock(string SubjectID, string VariableName, string callback)
        {
            int SessionID = InvitationManagement.GetSessionIDByInvitationID(SessionManagement.GetInvitationIDBySubjectID(SubjectID));

            if (Global.SessionVariables[SessionID].ContainsKey(VariableName) == false)
            {
                return false;
            }

            return Global.SessionVariables[SessionID][VariableName].Lock(SubjectID, callback);
        }

        /// <summary>
        /// Notify Subject that waits for a mutex (lock) that he got the mutex 
        /// </summary>
        /// <param name="SubjectID">Subject ID that mutex was taken from</param>
        /// <param name="callback">Subject ID that mutex is given to</param>
        public static void NotifyGotLock(string SubjectID, string callback)
        {
            IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MessageHub>();
            hubContext.Clients.Client(Global.SubjectConnetionDictionary[SubjectID]).invoke(callback);
        }

        /// <summary>
        /// Release lock of a variable and operating a callback
        /// </summary>
        /// <param name="SubjectID">Subject ID</param>
        /// <param name="VariableName">Variable Name</param>
        /// <param name="callback">callback</param>
        public void Release(string SubjectID, string VariableName, string callback)
        {
            int SessionID = InvitationManagement.GetSessionIDByInvitationID(SessionManagement.GetInvitationIDBySubjectID(SubjectID));

            if (Global.SessionVariables[SessionID].ContainsKey(VariableName) == false)
            {
                return;
            }

            if (Global.SessionVariables[SessionID][VariableName].Release(SubjectID))
            {
                Clients.Client(Global.SubjectConnetionDictionary[SubjectID]).invoke(callback);
            }
        }

        /// <summary>
        /// Checks if a variable is locked or free to use
        /// </summary>
        /// <param name="SubjectID">Subject ID</param>
        /// <param name="VariableName">Variable Name</param>
        /// <returns>True if free, False otherwise</returns>
        public Boolean IsLocked(string SubjectID, string VariableName)
        {
            int SessionID = InvitationManagement.GetSessionIDByInvitationID(SessionManagement.GetInvitationIDBySubjectID(SubjectID));

            if (Global.SessionVariables[SessionID].ContainsKey(VariableName) == false)
                return false;

            return Global.SessionVariables[SessionID][VariableName].isLocked;
        }

        /// <summary>
        /// Sends anew message to a recipient on a specific experiment stage
        /// </summary>
        /// <param name="stage">Experiment stage</param>
        /// <param name="recipient">Recipient</param>
        /// <param name="message">Message</param>
        public void Send(string stage, string recipient, string message)
        {
            if (Global.SubjectConnetionDictionary.ContainsKey(recipient))
            {
                string MessageID = BEELab.Data.CryptographyHelper.GenerateRandomString(10);

                if (Global.MessageBoxMutex.ContainsKey(recipient) == false)
                {
                    Global.MessageBoxMutex[recipient] = new Mutex(false);
                }

                Global.MessageBoxMutex[recipient].WaitOne();
                if (Global.MessageBox.ContainsKey(recipient) == false)
                {
                    Global.MessageBox[recipient] = new Dictionary<string, Dictionary<string, string>>();
                }

                if (Global.MessageBox[recipient].ContainsKey(stage) == false)
                {
                    Global.MessageBox[recipient][stage] = new Dictionary<string, string>();
                }

                Global.MessageBox[recipient][stage][MessageID] = message;
                Global.MessageBoxMutex[recipient].ReleaseMutex();

                Clients.Client(Global.SubjectConnetionDictionary[recipient]).acceptMessage(recipient, stage, message, MessageID);
            }
        }

        /// <summary>
        /// Sends new message to all users from specific subject
        /// </summary>
        /// <param name="SubjectID">Subject ID</param>
        /// <param name="stage">Stage</param>
        /// <param name="message">Message</param>
        public void SendToAll(string SubjectID, string stage, string message)
        {
            int SessionID = BEELab.Logic.ProgramManagement.SessionManagement.SessionManagement.GetSessionIDBySubjectID(SubjectID);
            foreach (string recipient in BEELab.Logic.ProgramManagement.SessionManagement.SessionManagement.GetSubjectList(SessionID))
            {
                Send(stage, recipient, message);
            }
        }

        /// <summary>
        /// Confirms an arrival of a message to subject ID and release mutex
        /// </summary>
        /// <param name="SubjectID">Subject ID</param>
        /// <param name="stage">Stage</param>
        /// <param name="messageID">Message ID</param>
        public void ConfirmDelivery(string SubjectID, string stage, string messageID)
        {
            Global.MessageBoxMutex[SubjectID].WaitOne();
            Global.MessageBox[SubjectID][stage].Remove(messageID);
            Global.MessageBoxMutex[SubjectID].ReleaseMutex();
        }

        /// <summary>
        /// Gets all user's messages
        /// </summary>
        /// <param name="SubjectID">Subject ID</param>
        /// <param name="stage">Stage</param>
        public void GetMessages(string SubjectID, string stage)
        {
            Global.MessageBoxMutex[SubjectID].WaitOne();
            foreach (string id in Global.MessageBox[SubjectID][stage].Keys)
            {
                Clients.Client(Global.SubjectConnetionDictionary[SubjectID]).acceptMessage(SubjectID, stage, Global.MessageBox[SubjectID][stage][id], id);
            }

            Global.MessageBoxMutex[SubjectID].ReleaseMutex();
        }

        /// <summary>
        /// Registers new message to a stage
        /// </summary>
        /// <param name="SubjectID">Subject ID</param>
        /// <param name="stage"></param>
        public void Register(string SubjectID, string stage)
        {
            Groups.Add(Context.ConnectionId, BEELab.Logic.ProgramManagement.SessionManagement.InvitationManagement.GetSessionIDByInvitationID(BEELab.Logic.ProgramManagement.SessionManagement.SessionManagement.GetInvitationIDBySubjectID(SubjectID)).ToString());

            Global.SubjectConnetionDictionary[SubjectID] = Context.ConnectionId;

            if (Global.MessageBoxMutex.ContainsKey(SubjectID) == false)
            {
                Global.MessageBoxMutex[SubjectID] = new Mutex(false);
            }

            Global.MessageBoxMutex[SubjectID].WaitOne();
            if (Global.MessageBox.ContainsKey(SubjectID) == false)
            {
                Global.MessageBox[SubjectID] = new Dictionary<string, Dictionary<string, string>>();
            }

            if (Global.MessageBox[SubjectID].ContainsKey(stage) == false)
            {
                Global.MessageBox[SubjectID][stage] = new Dictionary<string, string>();
            }
            Global.MessageBoxMutex[SubjectID].ReleaseMutex();
        }

        /// <summary>
        /// Inserts new record data into DB
        /// </summary>
        /// <param name="SubjectID">Subject ID</param>
        /// <param name="Stage">Stage</param>
        /// <param name="Label">Label</param>
        /// <param name="Value">Value</param>
        public void RecordData(string SubjectID, string Stage, string Label, string Value)
        {
            int SessionID = BEELab.Logic.ProgramManagement.SessionManagement.SessionManagement.GetSessionIDBySubjectID(SubjectID);

            int StageID = BEELab.Logic.ProgramManagement.StageManagement.StageManagement.GetIDByName(SessionManagement.GetProgramIDBySessionID(SessionID), Stage);

            SqlCommand command = new SqlCommand();
            command.CommandText = "INSERT INTO RecordedData (SubjectID, Label, Value) VALUES (@SubjectID, @Label, @Value)";
            command.Parameters.Add(new SqlParameter("@SubjectID", SubjectID));
            command.Parameters.Add(new SqlParameter("@Label", Stage + "_" + Label));
            command.Parameters.Add(new SqlParameter("@Value", Value));
            BEELab.Data.DBHelper.ExecuteNonQuery(command);
        }
    }
}